/**
 * MIT License
 * 
 * Copyright (c) 2024 - present @ ebraid
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

#include <drivers/gpio.h>
#include <drivers/platform.h>
#include <list.h>
#include <types.h>
#include <string.h>
#include <suniv.h>
#include <irqnum.h>
#include <drivers/irq.h>
#include <config.h>
#include <io.h>


// port = 0, 1, 2, 3, 4, 5
#define GPIO_BASE(port)             (0x01c20800 + (port) * 0x24)
#define CFG0_REG_OFFSET             (0x00)
#define CFG1_REG_OFFSET             (0x04)
#define CFG2_REG_OFFSET             (0x08)
#define CFG3_REG_OFFSET             (0x0C)
#define CFGX_REG_OFFSET(pin)        (((pin) >> 3) * 4)

#define DATA_REG_OFFSET             (0x10)

#define DRV0_REG_OFFSET             (0x14)
#define DRV1_REG_OFFSET             (0x18)
#define DRVX_REG_OFFSET(pin)        (0x14 + (((pin) >> 4) * 4))

#define PUL0_REG_OFFSET             (0x1C)
#define PUL1_REG_OFFSET             (0x20)
#define PULX_REG_OFFSET(pin)        (0x1C + (((pin) >> 4) * 4))

// port = 0(GPIOD), 1(GPIOE), 2(GPIOF)
#define GPIO_INT_BASE(port)         (0x1C20A00 + (port) * 0x20)
#define INT_CFG0_REG_OFFSET         (0x00)
#define INT_CFG1_REG_OFFSET         (0x04)
#define INT_CFG2_REG_OFFSET         (0x08)
#define INT_CFG3_REG_OFFSET         (0x0C)
#define INT_CFGX_REG_OFFSET(pin)    (((pin) >> 3) * 4)

#define INT_CTRLX_REG(port)          (0x1C20A00 + (port) * 0x20 + 0x10)
#define INT_STAX_REG(port)           (0x1C20A00 + (port) * 0x20 + 0x14)
#define INT_DEBX_REG(port)           (0x1C20A00 + (port) * 0x20 + 0x18)


static void suniv_set_clock(int16_t pin, int status)
{

}


/**
 * MUX: |   High 8bit   |    low 8bit   |
 * FUN: |   reserved    |      FUNC     |
 */
static void suniv_set_mux(int16_t pin, uint16_t mux)
{
    uint8_t func = mux & 0xff;
    uint32_t port = IO_TO_PORT(pin);
    uint32_t pin_x =  IO_TO_PIN(pin);

    size_t cfgr_val = read32(GPIO_BASE(port) + CFGX_REG_OFFSET(pin_x));
    cfgr_val &= ~(0xF << ((pin_x & 0x7) * 4));
    cfgr_val |= (func << ((pin_x & 0x7) * 4));
    write32(GPIO_BASE(port) + CFGX_REG_OFFSET(pin_x), cfgr_val);
}


static void suniv_set_dir(int16_t pin, uint8_t flag)
{
    uint32_t port = IO_TO_PORT(pin);
    uint32_t pin_x =  IO_TO_PIN(pin);

    size_t cfgr_val = read32(GPIO_BASE(port) + CFGX_REG_OFFSET(pin_x));
    cfgr_val &= ~(0xF << ((pin_x & 0x7) * 4));

    if(flag) { // output
        cfgr_val |= (1 << ((pin_x & 0x7) * 4));
    }

    write32(GPIO_BASE(port) + CFGX_REG_OFFSET(pin_x), cfgr_val);
}


static void suniv_set_drv(int16_t pin, uint8_t drv)
{
    uint32_t port = IO_TO_PORT(pin);
    uint32_t pin_x =  IO_TO_PIN(pin);

    size_t drvr_val = read32(GPIO_BASE(port) + DRVX_REG_OFFSET(pin_x));
    drvr_val &= ~(0x3 << ((pin_x & 0xF) * 2));
    drvr_val |= (drv << ((pin_x & 0xF) * 2));
    write32(GPIO_BASE(port) + DRVX_REG_OFFSET(pin_x), drvr_val);
}


static void suniv_set_pulldown(int16_t pin, uint8_t flag)
{
    uint32_t port = IO_TO_PORT(pin);
    uint32_t pin_x =  IO_TO_PIN(pin);

    size_t pulr_val = read32(GPIO_BASE(port) + PULX_REG_OFFSET(pin_x));
    pulr_val &= ~(0x3 << ((pin_x & 0xF) * 2));

    if(flag) {
        pulr_val |= (0x2 << ((pin_x & 0xF) * 2));
    }

    write32(GPIO_BASE(port) + PULX_REG_OFFSET(pin_x), pulr_val);
}


static void suniv_set_pullup(int16_t pin, uint8_t flag)
{
    uint32_t port = IO_TO_PORT(pin);
    uint32_t pin_x =  IO_TO_PIN(pin);

    size_t pulr_val = read32(GPIO_BASE(port) + PULX_REG_OFFSET(pin_x));
    pulr_val &= ~(0x3 << ((pin_x & 0xF) * 2));

    if(flag) {
        pulr_val |= (0x1 << ((pin_x & 0xF) * 2));
    }

    write32(GPIO_BASE(port) + PULX_REG_OFFSET(pin_x), pulr_val);
}


static void suniv_set_value(int16_t pin, uint8_t value)
{
    uint32_t port = IO_TO_PORT(pin);
    uint32_t pin_x =  IO_TO_PIN(pin);

    size_t datar_val = read32(GPIO_BASE(port) + DATA_REG_OFFSET);
    if(value) {
        datar_val |= (1 << pin_x);
    } else {
        datar_val &= ~(1 << pin_x);
    }
    write32(GPIO_BASE(port) + DATA_REG_OFFSET, datar_val);
}


static int suniv_get_value(int16_t pin)
{
    uint32_t port = IO_TO_PORT(pin);
    uint32_t pin_x =  IO_TO_PIN(pin);

    return (read32(GPIO_BASE(port) + DATA_REG_OFFSET) & BIT(pin_x)) ? 1 : 0;
}


static int suniv_set_exti(int16_t pin, int flag)
{
    uint32_t port = IO_TO_PORT(pin);
    uint32_t pin_x =  IO_TO_PIN(pin);
    uint32_t extir_val = read32(GPIO_INT_BASE(port - 3) + INT_CFGX_REG_OFFSET(pin_x));

    /* set external interrupt mode */
    suniv_set_mux(pin, 6);

    extir_val &= ~(0xF << (pin_x * 4));

    switch (flag) {
    case IRQF_RISING:
        extir_val |= (0 << (pin_x * 4));
        break;
    
    case IRQF_FALLING:
        extir_val |= (1 << (pin_x * 4));
        break;

    case IRQF_HIGH:
        extir_val |= (2 << (pin_x * 4));
        break;

    case IRQF_LOW:
        extir_val |= (3 << (pin_x * 4));
        break;
    
    case IRQF_EDGE:
        extir_val |= (4 << (pin_x * 4));
        break;
    default: 
    }

    write32(GPIO_INT_BASE(port - 3) + INT_CFGX_REG_OFFSET(pin_x), extir_val);
    /* enable interrupt */
    write32(INT_CTRLX_REG(port - 3), BIT(pin_x));

    switch (port) {
        case 0 ... 2: return -1;
        case 3: return IOD_IRQNUM;
        case 4: return IOE_IRQNUM;
        case 5: return IOF_IRQNUM;
        default: return -1;
    }
    return -1;
}


static struct gpio_ops suniv_ops = {
    .init = NULL,
    .set_clock = suniv_set_clock,
    .set_mux = suniv_set_mux,
    .set_dir = suniv_set_dir,
    .set_drv = suniv_set_drv,
    .set_pulldown = suniv_set_pulldown,
    .set_pullup = suniv_set_pullup,
    .set_value = suniv_set_value,
    .get_value = suniv_get_value,
    .set_exti = suniv_set_exti,
};


static struct gpio_desc suniv_gpio = {
    .name = "suniv-gpio",
    .npins = CONFIG_GPIO_PINS,
    .ops = &suniv_ops,
};


static int suniv_gpio_register(void)
{
    gpio_desc_register(&suniv_gpio);
    return 0;
}


base_initcall(suniv_gpio_register);
